package com.bzchao.ds.core.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @desc 动态数据源配置
 **/
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    /**
     * ThreadLocal 用于提供线程局部变量，在多线程环境可以保证各个线程里的变量独立于其它线程里的变量。
     * 也就是说 ThreadLocal 可以为每个线程创建一个【单独的变量副本】，相当于线程的 private static 类型变量。
     */
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    private static final Map<String, DruidDataSource> DB_RESOURCE_MAP = new ConcurrentHashMap<>();

    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    @Qualifier("masterDataSource")
    private DataSource masterDataSource;

    public static String getConnectionId() {
        return CONTEXT_HOLDER.get();
    }

    public static void switchConnect(String connectionId) {
        CONTEXT_HOLDER.set(connectionId);
    }

    public static void clearDataSource() {
        CONTEXT_HOLDER.remove();
    }

    public void setDataSources(List<Map<String, String>> dataSources) {
        //移除和关闭不存在的连接
        Set<String> newKeys = dataSources.stream().map(dataSource -> dataSource.get("connectId")).collect(Collectors.toSet());
        DB_RESOURCE_MAP.entrySet().forEach(entry -> {
            if (!newKeys.contains(entry.getKey())) {
                this.closeDataSource(entry.getKey());
            }
        });

        //创建和更新连接
        dataSources.stream().forEach(dataSource -> {
            this.addDataSource(dataSource.get("connectId"), dataSource);
        });
    }

    /**
     * 设置数据源
     */
    private void addDataSource(String connectionId, Map<String, String> dataSource) {
        if (CollectionUtils.isEmpty(dataSource)) {
            return;
        }
        DruidDataSource druidDataSource = DB_RESOURCE_MAP.get(connectionId);
        DruidDataSource defaultDruidDataSource = applicationContext.getBean(DruidDataSource.class);

        String url = dataSource.get("datasource.url");
        String username = dataSource.get("datasource.username");
        String password = dataSource.get("datasource.password");
        String driverClass = dataSource.get("datasource.driverClass");
        //判断当前数据源是否存在,若不存在则创建新数据源并放入DB_RESOURCE_MAP
        if (druidDataSource == null) {
            druidDataSource = new DruidDataSource();
            BeanUtils.copyProperties(defaultDruidDataSource, druidDataSource);
            druidDataSource.setUrl(url);
            druidDataSource.setUsername(username);
            druidDataSource.setPassword(password);
            druidDataSource.setDriverClassName(driverClass);
            DB_RESOURCE_MAP.put(connectionId, druidDataSource);
        } else {
            //若数据源已存在,判断当前数据源是否有改动.有改动则关闭原先数据源并更新数据源信息后放入map
            if (!username.equals(druidDataSource.getUsername()) ||
                    !password.equals(druidDataSource.getPassword()) ||
                    !url.equals(druidDataSource.getUrl())) {
                druidDataSource.close();

                druidDataSource = new DruidDataSource();
                BeanUtils.copyProperties(defaultDruidDataSource, druidDataSource);
                druidDataSource.setUrl(url);
                druidDataSource.setUsername(username);
                druidDataSource.setPassword(password);
                druidDataSource.setDriverClassName(driverClass);
                DB_RESOURCE_MAP.put(connectionId, druidDataSource);
            }
        }
        CONTEXT_HOLDER.set(connectionId);
    }

    /**
     * 关闭数据源
     */
    public void closeDataSource(String connectionId) {
        log.info("关闭数据据连接：{}", connectionId);
        if (DB_RESOURCE_MAP.containsKey(connectionId)) {
            DruidDataSource druidDataSource = DB_RESOURCE_MAP.get(connectionId);
            druidDataSource.close();
            DB_RESOURCE_MAP.remove(connectionId);
        }
    }

    @Override
    public void afterPropertiesSet() {
    }

    @Override
    protected Object determineCurrentLookupKey() {
        return getConnectionId();
    }

    @Override
    protected DataSource determineTargetDataSource() {
        String connectionId = getConnectionId();
        Assert.hasLength(connectionId, "租户ID不能为空");
        if ("master".equals(connectionId)) {
            return masterDataSource;
        }
        DruidDataSource druidDataSource = DB_RESOURCE_MAP.get(connectionId);

        Assert.notNull(druidDataSource, "找不到租户的数据库连接" + connectionId);
        return druidDataSource;
    }

}


